////////////////////////////////////////////////////////////////////////////
//
//  Crytek Engine Source File.
//  Copyright (C), Crytek Studios, 2002.
// -------------------------------------------------------------------------
//  File name:   ParticleEmitter.h
//  Version:     v1.00
//  Created:     18/7/2003 by Timur.
//  Compilers:   Visual Studio.NET
//  Description: 
// -------------------------------------------------------------------------
//  History:
//
////////////////////////////////////////////////////////////////////////////

#ifndef __particleemitter_h__
#define __particleemitter_h__
#pragma once

#ifdef _WIN32
#pragma warning(disable: 4355)
#endif

#include "ParticleEffect.h"
#include "ParticleEnviron.h"
#include "ParticleContainer.h"
#include "ParticleSubEmitter.h"
#include "ParticleUtils.h"
#include "ParticleManager.h"

#include <SpuUtils.h>

#undef PlaySound

class CParticle;

//////////////////////////////////////////////////////////////////////////
enum EEmitterState
{
	eEmitter_Dead,				// Inactive and no particles
	eEmitter_Dying,				// Permanently inactive, particles remain
	eEmitter_Dormant,			// Temporarily inactive
	eEmitter_Active,			// Currently active
};

//////////////////////////////////////////////////////////////////////////
// A top-level emitter system, interfacing to 3D engine
class CParticleEmitter : public IParticleEmitter, public CParticleSource
{
public:

	CParticleEmitter( const IParticleEffect* pEffect, const PivotLocation& loc, uint32 uEmitterFlags = 0, const SpawnParams* pSpawnParams = NULL  );
	~CParticleEmitter();

	//////////////////////////////////////////////////////////////////////////
	// IRenderNode implementation.
	//////////////////////////////////////////////////////////////////////////

	virtual void ReleaseNode()													{ Kill(); Register(false); }	
	virtual EERType GetRenderNodeType()									{ return eERType_ParticleEmitter; }
	virtual char const* GetName() const									{ return m_pTopEffect->GetName(); }
	virtual char const* GetEntityClassName() const			{ return "ParticleEmitter"; }
	virtual string GetDebugString(char type = 0) const;

	virtual Vec3 GetPos(bool bWorldOnly = true) const		{ return GetLocation().t; }
	virtual const AABB GetBBox() const									{ return m_bbWorld; }
	virtual void SetBBox( const AABB& WSBBox )					{ m_bbWorld = WSBBox; }
	virtual void GetLocalBounds( AABB& bbox );
	virtual void SetViewDistRatio(int nViewDistRatio) 
	{
		// Override to cache normalized value.
		IRenderNode::SetViewDistRatio(nViewDistRatio);
		m_fViewDistRatio = GetViewDistRatioNormilized();
	}
	ILINE float GetViewDistRatioFloat() const						{ return m_fViewDistRatio; }

	virtual float GetMaxViewDist()											{ return GetMaxAngularDensity(GetCamera()) * m_fMaxParticleSize; }

	virtual void SetMatrix( Matrix34 const& mat )				{ if (mat.IsValid()) SetLocation(QuatTS(mat)); }

	virtual void SetMaterial( IMaterial* pMaterial )		{ m_pMaterial = pMaterial; }
	virtual IMaterial* GetMaterial(Vec3 * pHitPos = NULL);							
	virtual IMaterial* GetMaterialOverride()						{ return m_pMaterial; }

	virtual IPhysicalEntity *GetPhysics() const					{ return 0; }
	virtual void SetPhysics(IPhysicalEntity *)					{}

	virtual void Render( SRendParams const& rParam );
	virtual void OnEntityEvent( IEntity* pEntity, SEntityEvent const& event );
	virtual void OnPhysAreaChange()											{ m_PhysEnviron.m_nNonUniformFlags &= ~EFF_LOADED; }

	virtual void GetMemoryUsage( ICrySizer* pSizer ) const;

	//////////////////////////////////////////////////////////////////////////
	// IParticleEmitter implementation.
	//////////////////////////////////////////////////////////////////////////
	VIRTUAL void SetEffect( IParticleEffect const* pEffect );
	VIRTUAL const IParticleEffect* GetEffect() const				{ return m_pTopEffect; };

	VIRTUAL void SetLocation( const PivotLocation& loc );

	ParticleTarget const& GetTarget() const									{ return m_Target; }
	VIRTUAL void SetTarget( ParticleTarget const& target )
	{
		if ((int)target.bPriority >= (int)m_Target.bPriority)
			m_Target = target;
	}
	VIRTUAL void SetSpawnParams( SpawnParams const& spawnParams, GeomRef geom = GeomRef() );
	VIRTUAL void GetSpawnParams( SpawnParams& spawnParams ) const	
																													{ spawnParams = m_SpawnParams; }
	VIRTUAL bool IsAlive() const														{ return SParticleMoveState::IsAlive(); }
	VIRTUAL bool IsInstant() const;
	VIRTUAL bool IsImmortal() const;
	VIRTUAL void Prime();
	VIRTUAL void Activate( bool bActive );
	VIRTUAL void Kill();
	VIRTUAL void Restart();
	VIRTUAL void Update();
	VIRTUAL void EmitParticle( const EmitParticleData* pData = NULL );
	VIRTUAL void SetEntity( IEntity* pEntity, int nSlot );
	VIRTUAL void OffsetPosition(const Vec3& delta);
	VIRTUAL bool UpdateStreamableComponents(float fImportance, Matrix34A&  objMatrix, IRenderNode * pRenderNode, float fEntDistance, bool bFullUpdate, int nLod);
	VIRTUAL EntityId GetAttachedEntityId()									{ return m_nEntityId; }
	VIRTUAL int GetAttachedEntitySlot()											{ return m_nEntitySlot; }
	VIRTUAL uint32 GetEmitterFlags() const									{ return m_nEmitterFlags; }
	VIRTUAL void SetEmitterFlags(uint32 flags)							
	{
		// Allow only custom flags to be set.
		const uint32 uInternalMask = ePEF_Custom-1;
		m_nEmitterFlags = (m_nEmitterFlags & uInternalMask) | (flags & ~uInternalMask);
	}
	VIRTUAL bool NeedsNearestCubemapUpdate(uint16 & nCachedId) const;
	VIRTUAL void CacheNearestCubemap(uint16 nCMTexId);

	//////////////////////////////////////////////////////////////////////////
	// Other methods.
	//////////////////////////////////////////////////////////////////////////
	const SpawnParams& GetSpawnParams() const
		{ return m_SpawnParams; }
	void SetEmitGeom(GeomRef const& geom);
	void UpdateEmitCountScale();
	float GetMaxAngularDensity(const CCamera& cam) const
	{
		// Max particles/radian, modified by emitter settings.
		float fGlobalMaxAngDensity = cam.GetViewSurfaceZ() / (cam.GetFov() * max(GetCVars()->e_ParticlesMinDrawPixels, 0.125f));
		return fGlobalMaxAngDensity * m_fViewDistRatio * GetParticleScale();
	}

	void SerializeState( TSerialize ser );
	void Register( bool b );
	ILINE float GetEmitCountScale() const
		{ return m_fEmitCountScale; }

	void UpdateEffects();

	EEmitterState GetState() const;
	void UpdateState();
	void UpdateResetAge();

	void AddEffect( CParticleContainer* pParentContainer, CParticleSubEmitter* pParentEmitter, CParticleEffect const* pEffect, bool bUpdate = true );
	void CreateIndirectEmitters( CParticleContainer* pParentContainer, CParticle* pParentPart );

	SPhysEnviron const& GetPhysEnviron() const
	{
		return m_PhysEnviron;
	}
	SVisEnviron const& GetVisEnviron() const
	{
		return m_VisEnviron;
	}
	void OnVisAreaDeleted( IVisArea* pVisArea )
	{
		m_VisEnviron.OnVisAreaDeleted(pVisArea);
	}

	void GetDynamicBounds( AABB& bb ) const
	{
		bb.Reset();
		for_all_ptrs (const CParticleContainer, c, m_Containers)
			bb.Add( c->GetDynamicBounds() );
	}
	ILINE uint32 GetEnvFlags() const
	{
		return m_nEnvFlags & CParticleManager::Instance()->GetAllowedEnvironmentFlags();
	}
	void AddEnvFlags(uint32 nFlags)
	{
		m_nEnvFlags |= nFlags;
	}

	Vec3 const& GetLocalPivot() const
	{
		return m_vLocalPivot;
	}

	SPU_NO_INLINE float GetParticleScale() const
	{
		// Somewhat obscure. But top-level emitters spawned from entities, 
		// and not attached to other objects, should apply the entity scale to their particles.
		if (!GetEmitGeom())
			return m_SpawnParams.fSizeScale * GetLocation().s;
		else
			return m_SpawnParams.fSizeScale;
	}

#ifdef SHARED_GEOM
	SInstancingInfo* GetInstancingInfo()
	{
		if (m_nInstInfosUsed == m_InstInfos.size())
			m_InstInfos.push_back();
		return &m_InstInfos[m_nInstInfosUsed++];
	}
#endif

	void InvalidateStaticBounds()												
	{
		m_bbWorld.Reset();
		for_all_ptrs (CParticleContainer, c, m_Containers)
		{
			float fStableTime = c->InvalidateStaticBounds();
			m_fBoundsStableAge = max(m_fBoundsStableAge, fStableTime);
		}
	}
	void RenderDebugInfo();
	IEntity* GetEntity() const;
	void UpdateFromEntity();
	uint32 IsIndependent() const
	{
		return m_nEmitterFlags & ePEF_Independent;
	}
	bool NeedSerialize() const
	{
		return (m_nEmitterFlags & ePEF_Independent) && !(m_nEmitterFlags & ePEF_TemporaryEffect);
	}
	float TimeNotRendered() const
	{
		return GetAge() - m_fAgeLastRendered;
	}

	void GetCounts( SParticleCounts& counts ) const	
	{
		for_all_ptrs (const CParticleContainer, c, m_Containers)
		{
			c->GetCounts( counts ); 
		}
	}

	ParticlePoolList<CParticleContainer> const& GetContainers() const
	{
		return m_Containers;
	}

	bool IsEditSelected() const
	{
	#if !defined(PS3) && !defined(XENON)
		if (gEnv->IsEditing())
		{
			if (IEntity* pEntity = GetEntity())
			{
				if (IEntityRenderProxy* pRenderProxy = (IEntityRenderProxy*)pEntity->GetProxy(ENTITY_PROXY_RENDER))
				{
					if (IRenderNode* pRenderNode = pRenderProxy->GetRenderNode())
						return (pRenderNode->GetRndFlags() & ERF_SELECTED) != 0;
				}
			}
		}
	#endif
		return false;
	}

	void SyncUpdateParticlesSPU();

	void UpdateAllParticleContainerSpu();

	void CollectSpuUsage( VecSpuUsageT &vecSpuUsage );
	
	void Reset()
	{
		Register(false);

		// Free unneeded memory.
		m_Containers.clear();
	}

	void SetUpdateParticlesJobState(JobManager::SJobState *pJobState)
	{
		m_pUpdateParticlesSPUState = pJobState;
	}

private:

	// Internal emitter flags, extend EParticleEmitterFlags
	enum EFlags
	{
		ePEF_HasPhysics						= BIT(6),
		ePEF_HasTarget						= BIT(7),
		ePEF_HasAttachment				= BIT(8),
		ePEF_NeedsEntityUpdate		= BIT(9),
		ePEF_Registered						= BIT(10),
		ePEF_BoundsCurrent				= BIT(11)
	};

	JobManager::SJobState*			m_pUpdateParticlesSPUState;	

	// Constant values, effect-related.
	_smart_ptr<CParticleEffect>	m_pTopEffect;
	_smart_ptr<IMaterial>				m_pMaterial;			// Override material for this emitter.

	// Cache values derived from the main effect.
	float												m_fMaxParticleSize;

	CTimeValue									m_timeLastUpdate;			// Track this to automatically update age.
	SpawnParams									m_SpawnParams;				// External settings modifying emission.
	float												m_fEmitCountScale;		// Composite particle count scale.
	float												m_fViewDistRatio;			// Normalised value of IRenderNode version.

	ParticlePoolList<CParticleContainer>		
															m_Containers;
	int													m_nContainerCount;		// Cached container count; container array may be cleared.
	uint32											m_nEnvFlags;					// Union of environment flags affecting emitters.
	uint32											m_nRenObjFlags;				// Union of render feature flags.
	ParticleTarget							m_Target;							// Target set from external source.

	Vec3												m_vLocalPivot;				// Pivot, for emission interpolation.
	AABB												m_bbWorld;						// World bbox.

	float												m_fAgeLastRendered;
	float												m_fBoundsStableAge;		// Next age at which bounds stable.
	float												m_fResetAge;					// Age to purge unseen particles.
	float												m_fStateChangeAge;		// Next age at which a container's state changes.
	float												m_fDeathAge;					// Age when all containers (particles) dead.

	// Entity connection params.
	int													m_nEntityId;
	int													m_nEntitySlot;

	uint32											m_nEmitterFlags;

	SPhysEnviron								m_PhysEnviron;				// Common physical environment (uniform forces only) for emitter.
	SVisEnviron									m_VisEnviron;

#ifdef SHARED_GEOM
	AutoPtrArray<SInstancingInfo>		m_InstInfos;							// For geom particle rendering.
	int															m_nInstInfosUsed;					// Infos used in a given frame (avoid clearing Infos array each frame).
#endif // SHARED_GEOM

	uint16											m_nNearestCubemapId;
	int													m_nLastCubemapUpdateFrameId;

	// Functions.
	void ResetUnseen();
	void AllocEmitters();
	void UpdateContainers();
	void UpdateTimes();

	CParticleContainer* AddContainer( CParticleContainer* pParentContainer, const CParticleEffect* pEffect );
};

#endif // __particleemitter_h__
